package com.inepex.inechart.client.axes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.TreeMap;

import com.inepex.inechart.client.Defaults;
import com.inepex.inechart.shared.CartesianAssist;
import com.inepex.inechart.shared.CartesianException;
import com.inepex.inechart.shared.axes.Axis;
import com.inepex.inechart.shared.axes.AxisDirection;
import com.inepex.inechart.shared.axes.AxisPosition;
import com.inepex.inechart.shared.axes.Tick;
import com.inepex.inechart.shared.properties.Color;
import com.inepex.inechart.shared.properties.LineProperties;
import com.inepex.inechart.shared.util.PaddingUtil;
import com.inepex.inegraphics.impl.client.IncrementalProgress;
import com.inepex.inegraphics.impl.client.TextPositioner;
import com.inepex.inegraphics.shared.Context;
import com.inepex.inegraphics.shared.DrawingAreaAssist;
import com.inepex.inegraphics.shared.GraphicalObjectContainer;
import com.inepex.inegraphics.shared.gobjects.GraphicalObject;
import com.inepex.inegraphics.shared.gobjects.Line;
import com.inepex.inegraphics.shared.gobjects.Rectangle;
import com.inepex.inegraphics.shared.gobjects.Text;
import com.inepex.inegraphics.shared.gobjects.Text.BasePointXPosition;
import com.inepex.inegraphics.shared.gobjects.Text.BasePointYPosition;

/**
 * 
 * Client side class designed for responsive axis padding calculation,
 * and {@link GraphicalObject} creation.
 * Usage:
 * If you want to calculate padding for an axis, initialize it for partial padding calculation.
 * This progress stores the textual objects of ticks for faster completion of the next progress, 
 * the {@link GraphicalObject} creation.
 * 
 * @author Miklos Suveges / Inepex Ltd.
 *
 */
public class AxisCalculatorGWT implements IncrementalProgress{

	protected static final int minTickCountPerUpdatePart = 4;

	protected TreeMap<Tick, Text> tickTextMap;
	protected IncrementalProgressCaller updater;
	protected Axis axis;
	protected Axis perpAxis;
	protected double from;
	protected double to;
	protected ArrayList<Tick> ticksToCalc;
	protected int totalTickCount;
	protected int calcedTickCount;
	protected double[] mergedTickPaddings;

	protected GraphicalObject axisLine;
	protected double[] axisStartCoord;
	protected double[] axisEndCoord;
	protected GraphicalObjectContainer tickLines;
	protected GraphicalObjectContainer gridLinesAndFills;
	protected GraphicalObjectContainer texts;
	
	protected boolean paddingCalculation;

	public AxisCalculatorGWT(Axis axis) {
		this.axis = axis;
		tickTextMap = new TreeMap<Tick, Text>();
		ticksToCalc = new ArrayList<Tick>();
		perpAxis = axis.getPerpendicularAxis();
		tickLines = new GraphicalObjectContainer();
		gridLinesAndFills = new GraphicalObjectContainer();
		texts = new GraphicalObjectContainer();
	}
	
	protected void checkPerpAxis(boolean throwException) throws CartesianException{
		if(perpAxis == null){
			perpAxis = axis.getPerpendicularAxis();
			if(perpAxis == null){
				throw new CartesianException();
			}
		}
	}

	public void initForGOCreation(double from, double to) throws CartesianException{
		checkPerpAxis(true);
		this.from = from;
		this.to = to;
		paddingCalculation = false;
		tickLines.removeAllGraphicalObjects();
		gridLinesAndFills.removeAllGraphicalObjects();
		texts.removeAllGraphicalObjects();
		createTexts();
		createAxisLine();
		createFillBetweenTicks();
	}
	
	protected void createTexts(){
		for (Tick tick : tickTextMap.keySet()) {
			if(tick.getPosition() < from){
				continue;
			}
			if(tick.getPosition() > to){
				break;
			}
			texts.add(tickTextMap.get(tick));
		}
	}
	
	public void initForPaddingCalc(double from, double to) throws CartesianException{
		checkPerpAxis(true);
		this.from = from;
		this.to = to;
		mergedTickPaddings = new double[]{0,0,0,0};
		paddingCalculation = true;
	}

	@Override
	public void initProgress(IncrementalProgressCaller updater) {
		this.updater = updater;
		ticksToCalc.clear();
		Collections.sort(axis.getTicks());
		for(Tick t : axis.getTicks()){
			if(t.getPosition() < from){
				continue;
			}
			if(t.getPosition() > to){
				break;
			}
			ticksToCalc.add(t);
		}
		totalTickCount = ticksToCalc.size();
		calcedTickCount = 0;
	}

	@Override
	public boolean nextProgressPart() {
		long updatePartStart = System.currentTimeMillis();
		int actualTickCount = 0;
		Iterator<Tick> tickIterator = ticksToCalc.iterator();
		while(tickIterator.hasNext()){
			if(actualTickCount >= minTickCountPerUpdatePart && updater.getProgressPartTresholdMS() < System.currentTimeMillis() - updatePartStart){
				break;
			}
			Tick actual = tickIterator.next();
			if(paddingCalculation){
				double[] paddingForTick = getPadddingForTick(actual, axis, true);
				mergedTickPaddings = PaddingUtil.mergePaddings(mergedTickPaddings, paddingForTick);
			}
			else{
				createTickGOs(actual);
			}
			actualTickCount++;
			tickIterator.remove();
		}	
		calcedTickCount += actualTickCount;
		boolean finished = totalTickCount == calcedTickCount;
		return !finished;
	}

	@Override
	public void cancelProgress() {
		ticksToCalc.clear();
	}

	@Override
	public double getProgressRate() {
		return calcedTickCount / totalTickCount;
	}

	public double[] getMergedTickPaddings() {
		return mergedTickPaddings;
	}
	
	/*padding calculation */
	
	/**
	 * Calculates the extension from the position of the tick on the axis
	 * @param tick
	 * @param onHorizontalAxis
	 * @param includeText
	 * @return
	 */
	public double[] getPadddingForTick(Tick tick, Axis axis, boolean includeText){
		int[] textPadding = measureTickText(includeText ? tick : null, true);
		double top = 0, right = 0, bot = 0, left = 0;

		if(axis.isHorizontal()){
			switch (tick.getTickPosition()) {
			case Cross:
				top = bot = tick.getTickLength() / 2;
				break;
			case To_Higher_Values:
				if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
					bot = tick.getTickLength();
				} else {
					top = tick.getTickLength();
				}
				break;
			case To_Lower_Values:
				if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
					bot = tick.getTickLength();
				} else {
					top = tick.getTickLength();
				}
				break;
			}
			if(textPadding[0] > 0){
				switch (tick.getTextContainer().getHorizontalPosition()) {
				case Auto:
				case Middle:
					left = right = textPadding[0] / 2;
					break;
				case Left:
					left = textPadding[0];
					break;
				case Right:
					right = textPadding[0];
					break;
				}
				switch (tick.getTextContainer().getVerticalPosition()) {
				case Auto:
					//tick text above axis
					if (axis.getAxisPosition() == AxisPosition.Maximum && perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Top ||
					axis.getAxisPosition() == AxisPosition.Minimum && perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
						top +=  textPadding[1] + tick.getTextContainerMargin();
					}
					//text below axis
					else {
						bot += tick.getTextContainerMargin() + textPadding[1];
					}
				case Middle:
					top = Math.max(textPadding[1] / 2, top);
					bot = Math.max(textPadding[1] / 2, bot);
					break;
				case Top:
					top +=  textPadding[1] + tick.getTextContainerMargin();
					break;
				case Bottom:
					bot += tick.getTextContainerMargin() + textPadding[1];
					break;
				}
			}
		}
		else{
			switch (tick.getTickPosition()) {
			case Cross:
				left = right = tick.getTickLength() / 2;
				break;
			case To_Higher_Values:
				if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left) {
					left = tick.getTickLength();
				} else {
					right = tick.getTickLength();
				}
				break;
			case To_Lower_Values:
				if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Right) {
					left = tick.getTickLength();
				} else {
					right = tick.getTickLength();
				}
				break;
			}
			if(textPadding[1] > 0){
				switch (tick.getTextContainer().getVerticalPosition()) {
				case Auto:
				case Middle:
					top = bot = textPadding[1] / 2;
					break;
				case Top:
					top = textPadding[1];
					break;
				case Bottom:
					bot = textPadding[1];
					break;
				}
				switch (tick.getTextContainer().getHorizontalPosition()) {
				case Auto:
					//tick text above axis
					if (axis.getAxisPosition() == AxisPosition.Maximum && perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left ||
					axis.getAxisPosition() == AxisPosition.Minimum && perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Right) {
						left +=  textPadding[0] + tick.getTextContainerMargin();
					}
					//text below axis
					else {
						right += tick.getTextContainerMargin() + textPadding[0];
					}
				case Middle:
					left = Math.max(textPadding[0] / 2, left);
					right = Math.max(textPadding[0] / 2, right);
					break;
				case Left:
					left +=  textPadding[0] + tick.getTextContainerMargin();
					break;
				case Right:
					right += tick.getTextContainerMargin() + textPadding[0];
					break;
				}
			}
		}

		return new double[]{top, right, bot, left};
	}

	public int[] measureTickText(Tick tick, boolean putInTickTextMap){
		if(tick == null || (tick.getText() != null && tick.getText().getText().length() > 0)){
			return new int[]{0,0};
		}
		Text text = new Text(tick.getText().getText(), 0, 0);
		text.setFontFamily(tick.getText().getTextProperties().getFontFamily());
		text.setFontStyle(tick.getText().getTextProperties().getFontStyle());
		text.setFontWeight(tick.getText().getTextProperties().getFontWeight());
		text.setFontSize(tick.getText().getTextProperties().getFontSize());
		TextPositioner.measureText(text);
		int width = (int) (text.getWidth() +
				tick.getTextContainer().getLeftPadding() + tick.getTextContainer().getRightPadding() +
				tick.getTextContainer().getBackground().getLineProperties().getLineWidth() * 2) + 1;
		int height = (int) (text.getHeight() + 
				tick.getTextContainer().getTopPadding() + tick.getTextContainer().getBottomPadding() +
				tick.getTextContainer().getBackground().getLineProperties().getLineWidth() * 2) + 1;
		if(putInTickTextMap){
			text.setWidth(width);
			text.setHeight(height);
			tickTextMap.put(tick, text);
		}
		return new int[]{width, height};
	}

	/*GO creation*/

	public void createAxisLine(){
		double startX = 0, startY = 0, endX = 0, endY = 0;
		// determine the axis' position
		CartesianAssist cartesianAssist =  axis.getModulToAlign().getCartesianAssist();
		switch (axis.getAxisPosition()) {
		case Minimum:
			if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left) {
				startX = endX = cartesianAssist.getChartWidth() - axis.getModulToAlign().getRightPadding();
			} else if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Right) {
				startX = endX = axis.getModulToAlign().getLeftPadding();
			} else if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
				startY = endY = axis.getModulToAlign().getTopPadding();
			} else if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Top) {
				startY = endY = cartesianAssist.getChartHeight() - axis.getModulToAlign().getBottomPadding();
			}
			break;
		case Maximum:
			if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Right) {
				startX = endX = cartesianAssist.getChartWidth() - axis.getModulToAlign().getRightPadding();
			} else if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left) {
				startX = endX = axis.getModulToAlign().getLeftPadding();
			} else if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Top) {
				startY = endY = axis.getModulToAlign().getTopPadding();
			} else if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
				startY = endY = cartesianAssist.getChartHeight() - axis.getModulToAlign().getBottomPadding();
			}
			break;
		case Middle:
			if (perpAxis.isHorizontal()) {
				startX = endX = cartesianAssist.getWidth() / 2 + axis.getModulToAlign().getLeftPadding();
			} else {
				startY = endY = cartesianAssist.getHeight() / 2 + axis.getModulToAlign().getTopPadding();
			}
			break;
		case Fixed:
			if (axis.getFixedPosition() >= perpAxis.getMin() && axis.getFixedPosition() <= perpAxis.getMax()) {
				if (perpAxis.isHorizontal()) {
					startX = endX = cartesianAssist.modelToCanvasHorizontal(axis.getFixedPosition());
				} else {
					startY = endY = cartesianAssist.modelToCanvasVertical(axis.getFixedPosition());
				}
			} else
				axis.setVisible(false);
			break;
		case Fixed_Dock_If_Not_Visible:
			double pos = axis.getFixedPosition();
			if (axis.getFixedPosition() < perpAxis.getMin()) {
				pos = perpAxis.getMin();
			} else if (axis.getFixedPosition() > perpAxis.getMax()) {
				pos = perpAxis.getMax();
			}
			if (perpAxis.isHorizontal()) {
				startX = endX = cartesianAssist.modelToCanvasHorizontal(pos);
			} else {
				startY = endY = cartesianAssist.modelToCanvasVertical(pos);
			}
			break;
		}
		if (perpAxis.isHorizontal()) {
			if (axis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
				startY = axis.getModulToAlign().getTopPadding();
				endY = cartesianAssist.getChartHeight() - axis.getModulToAlign().getBottomPadding();
			} else if (axis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Top) {
				endY = axis.getModulToAlign().getTopPadding();
				startY = cartesianAssist.getChartHeight() - axis.getModulToAlign().getBottomPadding();
			}
		} else {
			if (axis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left) {
				startX = cartesianAssist.getChartWidth() - axis.getModulToAlign().getRightPadding();
				endX = axis.getModulToAlign().getLeftPadding();
			} else if (axis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Right) {
				endX = cartesianAssist.getChartWidth() - axis.getModulToAlign().getRightPadding();
				startX = axis.getModulToAlign().getLeftPadding();
			}
		}
		Line axisLine = null;
		// TODO upper-, lower End
		if (axis.getLineProperties() != null) {
			axisLine = new Line(startX, startY, endX, endY, createContext(axis.getLineProperties()), axis.getZIndex());

		}
		this.axisLine = axisLine;
		axisStartCoord = new double[]{startX, startY};
		axisEndCoord = new double[]{endX, endY};
	}

	public void createTickGOs(Tick tick){
		CartesianAssist cartesianAssist =  axis.getModulToAlign().getCartesianAssist();
		// calculate the position
		double tickStartX = 0, tickStartY = 0, tickEndX = 0, tickEndY = 0, gridStartX, gridStartY, gridEndX, gridEndY;

		if (axis.isHorizontal()) {
			gridEndX = gridStartX = tickStartX = tickEndX = cartesianAssist.modelToCanvasHorizontal(tick.getPosition());
			gridStartY = axis.getModulToAlign().getTopPadding();
			gridEndY = cartesianAssist.getChartHeight() - axis.getModulToAlign().getBottomPadding();
			switch (tick.getTickPosition()) {
			case Cross:
				tickStartY = axisStartCoord[1] - tick.getTickLength() / 2;
				tickEndY = tickStartY + tick.getTickLength();
				break;
			case To_Higher_Values:
				tickStartY = axisStartCoord[1];
				if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
					tickEndY = tickStartY - tick.getTickLength();
				} else {
					tickEndY = tickStartY + tick.getTickLength();
				}
				break;
			case To_Lower_Values:
				tickStartY = axisStartCoord[1];
				if (perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom) {
					tickEndY = tickStartY + tick.getTickLength();
				} else {
					tickEndY = tickStartY - tick.getTickLength();
				}
				break;
			}
		} 
		else {
			gridEndY = gridStartY = tickStartY = tickEndY = cartesianAssist.modelToCanvasVertical(tick.getPosition());
			gridStartX = axis.getModulToAlign().getLeftPadding();
			gridEndX = cartesianAssist.getChartWidth() - axis.getModulToAlign().getRightPadding();
			switch (tick.getTickPosition()) {
			case Cross:
				tickStartX = axisStartCoord[0] - tick.getTickLength() / 2;
				tickEndX = tickStartX + tick.getTickLength();
				break;
			case To_Lower_Values:
				tickStartX = axisStartCoord[0];
				if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left) {
					tickEndX = tickStartX + tick.getTickLength();
				} else {
					tickEndX = tickStartX - tick.getTickLength();
				}
				break;
			case To_Higher_Values:
				tickStartX = axisStartCoord[0];
				if (perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left) {
					tickEndX = tickStartX - tick.getTickLength();
				} else {
					tickEndX = tickStartX + tick.getTickLength();
				}
				break;
			}
		}
		// Tick line
		if (tick.getTickLine() != null && tick.getTickLength() > 0) {
			Line tickLine = new Line(tickStartX, tickStartY, tickEndX, tickEndY, createContext(tick.getTickLine()), axis.getZIndex());
			tickLines.add(tickLine);
		}
		// Grid line
		if (tick.getGridLine() != null) {
			Line gridLine = new Line(gridStartX, gridStartY, gridEndX, gridEndY, createContext(tick.getGridLine()), axis.getZIndex());
			if(tick.getGridLine().getDashDistance() > 0){
				gridLinesAndFills.add(DrawingAreaAssist.createDashedLine(gridLine, tick.getGridLine().getDashStrokeLength(), tick.getGridLine().getDashDistance()));
			}
			else{
				gridLinesAndFills.add(gridLine);
			}
		}
		createTickText(tick, tickStartX, tickStartY);
	}

	public void createTickText(Tick tick, double tickStartX, double tickStartY){
		Text tickText = tickTextMap.get(tick);
		if(tickText == null){
			return;
		}
		//calculate the text position relative to tick
		BasePointXPosition h = null;
		BasePointYPosition v = null;
		switch (tick.getTextContainer().getHorizontalPosition()) {
		case Auto:
			if (axis.isHorizontal()) {
				h = BasePointXPosition.MIDDLE;
			} 
			else {
				if (axis.getAxisPosition() == AxisPosition.Maximum && perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Left ||
						axis.getAxisPosition() == AxisPosition.Minimum && perpAxis.getAxisDirection() == AxisDirection.Horizontal_Ascending_To_Right) {
					h = BasePointXPosition.RIGHT;
					tickStartX -= tick.getTextContainerMargin();
				}
				else {
					h = BasePointXPosition.LEFT;
					tickStartX += tick.getTickLength() + tick.getTextContainerMargin();
				}
			}
			break;
		case Left:
			h = BasePointXPosition.RIGHT;
			break;
		case Right:
			h = BasePointXPosition.LEFT;
			break;
		case Middle:
			h = BasePointXPosition.MIDDLE;
			break;
		}
		switch (tick.getTextContainer().getVerticalPosition()) {
		case Auto:
			if (!axis.isHorizontal()) {
				v = BasePointYPosition.MIDDLE;
			}
			else {
				if (axis.getAxisPosition() == AxisPosition.Maximum && perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Bottom ||
						axis.getAxisPosition() == AxisPosition.Minimum && perpAxis.getAxisDirection() == AxisDirection.Vertical_Ascending_To_Top) {
					v = BasePointYPosition.TOP;
					tickStartY += tick.getTickLength() + tick.getTextContainerMargin();
				} 
				else {
					v = BasePointYPosition.BOTTOM;
					tickStartY += tick.getTextContainerMargin();
				}
			}
			break;
		case Bottom:
			v = BasePointYPosition.BOTTOM;
			break;
		case Middle:
			v = BasePointYPosition.MIDDLE;
			break;
		case Top:
			v = BasePointYPosition.TOP;
			break;
		}
		tickText.setBasePointX(tickStartX);
		tickText.setBasePointY(tickStartY);
		tickText.setBasePointYPosition(v);
		tickText.setBasePointXPosition(h);
	}

	public void createFillBetweenTicks(){
		double x, y, width, height;
		CartesianAssist cartesianAssist =  axis.getModulToAlign().getCartesianAssist();
		for (Object[] tickPair : axis.getGridFills()) {
			if(((Tick) tickPair[0]).getPosition() >= to || ((Tick) tickPair[1]).getPosition() <= from)
				continue;
			double tick1,tick2;
			if (axis.isHorizontal()) {
				tick1 = Math.max(cartesianAssist.modelToCanvasHorizontal(((Tick) tickPair[0]).getPosition()), axis.getModulToAlign().getLeftPadding());
				tick2 = Math.min(cartesianAssist.modelToCanvasHorizontal(((Tick) tickPair[1]).getPosition()), axis.getModulToAlign().getCartesianAssist().getRightEnd());
//				x = Math.max(axis.modulToAlign.getLeftPadding(), Math.min(tick1, tick2));
				x = tick1;
				y = axis.getModulToAlign().getTopPadding();
				height = axis.getModulToAlign().getCartesianAssist().getHeight();
				width = tick2 - tick1;
//				width = Math.min(Math.abs(tick2 - tick1), canvas.getWidth() - axis.modulToAlign.getRightPadding() - x);
			}
			else { //TODO
				tick1 = cartesianAssist.modelToCanvasVertical(((Tick) tickPair[0]).getPosition());
				tick2 = cartesianAssist.modelToCanvasVertical(((Tick) tickPair[1]).getPosition());
				x = axis.getModulToAlign().getLeftPadding();
				y = Math.max(axis.getModulToAlign().getTopPadding(), Math.min(tick1, tick2));
				width = axis.getModulToAlign().getCartesianAssist().getWidth();
				height = Math.min(Math.abs(tick1 - tick2), cartesianAssist.getChartHeight() - axis.getModulToAlign().getBottomPadding() - y);
			}

			Rectangle fill = new Rectangle(
					x,
					y,
					width,
					height,
					0,
					false,
					true,
					createFillContext((Color) tickPair[2]),
					axis.getZIndex());
			gridLinesAndFills.add(fill);
		}
	}

	protected Context createContext(LineProperties lineProperties) {
		return new Context(
				lineProperties.getLineColor().getAlpha(),
				lineProperties.getLineColor().getColor(),
				lineProperties.getLineWidth(),
				Defaults.colorString,
				0,
				0,
				Defaults.alpha,
				Defaults.colorString);
	}

	protected Context createFillContext(Color color) {
		return new Context(
				color.getAlpha(),
				color.getColor(),
				0,
				color.getColor(),
				0,
				0,
				Defaults.alpha,
				Defaults.colorString);
	}

	public GraphicalObject getAxisLine() {
		return axisLine;
	}

	public GraphicalObjectContainer getTickLines() {
		return tickLines;
	}

	public GraphicalObjectContainer getGridLinesAndFills() {
		return gridLinesAndFills;
	}
	
	public GraphicalObjectContainer getAxisLineAndTickGOs(){
		GraphicalObjectContainer goc = new GraphicalObjectContainer();
		if(axisLine != null){
			goc.add(axisLine);
		}
		if(tickLines != null){
			goc.add(tickLines);
		}
		return goc;
	}

	public GraphicalObjectContainer getTexts() {
		return texts;
	}
}
